
#include <BRender>
#include <BKeyEvent>
#include <BMouseEvent>
#include <BRoundHandler>

#include <BCamera>
#include <BViewer>
#include <BShape>
#include <BMaterial>
#include <BOpenGL>
#include <BSystem>
#include <BMesh>
#include <BSphere>

#include "operas/RemoveOpera.h"
#include "TransformHandler.h"

TransformHandler::TransformHandler()
{
	_dirtyAxis = false;
	_axisSize = 0;
	_handleMode = HM_None;
	_handleAxis = HA_None;
	_leftPressed = false;
	_circleSegments = 36;

	_cone_x = new BCone();
	_cone_x->setDepthTest(false);
	_cone_y = new BCone();
	_cone_y->setDepthTest(false);
	_cone_z = new BCone();
	_cone_z->setDepthTest(false);

	_rotateSphere = new BSphere(0.1f);
	{
		_rotateSphere->setLighting(true);
		_rotateSphere->setLineSmooth(true);
		_rotateSphere->setDepthTest(true);
		_rotateSphere->setCullback(true);

		BMaterial* material = new BMaterial();
		material->setAmbient(66, 66, 66, 156);
		material->setDiffuse(200, 128, 128, 156);
		material->setSpecular(200, 128, 128, 156);
		material->setShininess(60);

		_rotateSphere->setMaterial(material);
	}

	connect("target-changed", &TransformHandler::slotTargetChanged);
	connect("camera-focus-finished", &TransformHandler::slotCameraFocusFinished);

}
TransformHandler::~TransformHandler()
{

}

void TransformHandler::setConfig(Config* config)
{
	_config = config;
}
Config* TransformHandler::config() const
{
	return _config;
}

void TransformHandler::setHandleMode(HandleMode mode)
{
	if (_handleMode != mode)
	{
		_handleMode = mode;
		_dirtyAxis = true;
		this->fresh();
	}
}
HandleMode TransformHandler::handleMode() const
{
	return _handleMode;
}

void TransformHandler::setBasicMatrix(const BMatrix& basicMatrix)
{
	_basicMatrix = basicMatrix;
	_dirtyAxis = true;
	this->fresh();
}
const BMatrix& TransformHandler::basicMatrix() const
{
	return _basicMatrix;
}

void TransformHandler::setOrigin(const BMatrix& origin)
{
	_origin = origin;
	_dirtyAxis = true;
	this->fresh();
}
const BMatrix& TransformHandler::origin() const
{
	return _origin;
}

void TransformHandler::setMatrix(const BMatrix& matrix)
{
	_matrix = matrix;
	_dirtyAxis = true;
	transforming(_matrix);
	this->fresh();
}
const BMatrix& TransformHandler::matrix() const
{
	return _matrix;
}

void TransformHandler::dirtyAxis()
{
	_dirtyAxis = true;
	this->fresh();
}

void TransformHandler::freshMoveAxis()
{
	BVector center = _matrix.position();

	_axis_x.set(center, center + BVector(_axisSize, 0, 0));
	_axis_y.set(center, center + BVector(0, _axisSize, 0));
	_axis_z.set(center, center + BVector(0, 0, _axisSize));

	BReal coneSize = _axisSize * (BReal)0.1;
	_cone_x->setRadius(coneSize / 2);
	_cone_x->setHeight(coneSize);
	_cone_x->setPosition(_axis_x.end());
	_cone_x->setRotate(BQuater(BVector(0, 0, 1), BVector(1, 0, 0)));

	_cone_y->setRadius(coneSize / 2);
	_cone_y->setHeight(coneSize);
	_cone_y->setPosition(_axis_y.end());
	_cone_y->setRotate(BQuater(BVector(0, 0, 1), BVector(0, 1, 0)));

	_cone_z->setRadius(coneSize / 2);
	_cone_z->setHeight(coneSize);
	_cone_z->setPosition(_axis_z.end());

	BReal quadSize = _axisSize / (BReal)3.0;
	_quad_xy.a() = center;
	_quad_xy.b() = center + BVector(quadSize, 0, 0);
	_quad_xy.c() = center + BVector(quadSize, quadSize, 0);
	_quad_xy.d() = center + BVector(0, quadSize, 0);

	_quad_yz.a() = center;
	_quad_yz.b() = center + BVector(0, quadSize, 0);
	_quad_yz.c() = center + BVector(0, quadSize, quadSize);
	_quad_yz.d() = center + BVector(0, 0, quadSize);

	_quad_xz.a() = center;
	_quad_xz.b() = center + BVector(quadSize, 0, 0);
	_quad_xz.c() = center + BVector(quadSize, 0, quadSize);
	_quad_xz.d() = center + BVector(0, 0, quadSize);

}
void TransformHandler::freshRotateAxis()
{
	BReal unit = 2 * PIf / _circleSegments;

	_rotateCircleXY.resize(_circleSegments);
	for (int i = 0; i < _circleSegments; i++)
	{
		BReal x = cos(i * unit) * _axisSize;
		BReal y = sin(i * unit) * _axisSize;
		_rotateCircleXY[i] = BVector(x, y, 0);
	}

	_rotateCircleYZ.resize(_circleSegments);
	for (int i = 0; i < _circleSegments; i++)
	{
		BReal y = cos(i * unit) * _axisSize;
		BReal z = sin(i * unit) * _axisSize;
		_rotateCircleYZ[i] = BVector(0, y, z);
	}

	_rotateCircleXZ.resize(_circleSegments);
	for (int i = 0; i < _circleSegments; i++)
	{
		BReal x = cos(i * unit) * _axisSize;
		BReal z = sin(i * unit) * _axisSize;
		_rotateCircleXZ[i] = BVector(x, 0, z);
	}

	_rotateSphere->setRadius(_axisSize * (BReal)0.96);
}
void TransformHandler::freshScaleAxis()
{
	BVector center = _matrix.position();

	_axis_x.set(center, center + BVector(_axisSize, 0, 0));
	_axis_y.set(center, center + BVector(0, _axisSize, 0));
	_axis_z.set(center, center + BVector(0, 0, _axisSize));

	BReal length = _axisSize * (BReal)0.05;
	_box_x.reset(_axis_x.end(), length, length, length);
	_box_y.reset(_axis_y.end(), length, length, length);
	_box_z.reset(_axis_z.end(), length, length, length);

}

bool TransformHandler::pickMoveAxis(const BLine& line)
{
	BMatrix invMatrix = _basicMatrix.inverse();
	BLine localLine = line * invMatrix;

	HandleAxis axis = HA_None;
	float ratio = 1;
	float ratio_xy = 0;
	if (localLine.intersect(_quad_xy, ratio_xy) && ratio_xy < ratio)
	{
		axis = HA_XY;
		ratio = ratio_xy;
	}
	float ratio_yz = 0;
	if (localLine.intersect(_quad_yz, ratio_yz) && ratio_yz < ratio)
	{
		axis = HA_YZ;
		ratio = ratio_yz;
	}
	float ratio_xz = 0;
	if (localLine.intersect(_quad_xz, ratio_xz) && ratio_xz < ratio)
	{
		axis = HA_XZ;
	}
	if (!axis)
	{
		BReal disX = localLine.distance(_axis_x);
		BReal disY = localLine.distance(_axis_y);
		BReal disZ = localLine.distance(_axis_z);
		BReal pickRange = _axisSize / 20;
		if (disX < pickRange)
			axis = HA_X;
		if (disY < pickRange)
			axis = HA_Y;
		if (disZ < pickRange)
			axis = HA_Z;
	}
	if (_handleAxis != axis)
	{
		_handleAxis = axis;
		this->fresh();
	}
	return _handleAxis != HA_None;
}
bool TransformHandler::pickRotateAxis(const BLine& line)
{
	HandleAxis axis = HA_None;

	BQuater quat = _matrix.rotate();
	BVector center = _matrix.position();

	BVector foot = line.foot(center);
	BReal dist = (foot - center).length();
	if (dist < _axisSize)
	{
		BReal len = sqrt(_axisSize * _axisSize - dist * dist);
		BVector point = foot - line.direct() * len;

		BReal range = this->viewer()->unit(center) * 10;
		BVector axis_z = quat * BVector(0, 0, 1);
		BVector axis_y = quat * BVector(0, 1, 0);
		BVector axis_x = quat * BVector(1, 0, 0);

		BPlane plane_xy(axis_z, center);
		BReal dist_xy = abs(plane_xy.distance(point));

		BPlane plane_yz(axis_x, center);
		BReal dist_yz = abs(plane_yz.distance(point));

		BPlane plane_xz(axis_y, center);
		BReal dist_xz = abs(plane_xz.distance(point));

		if (dist_xy < range && dist_xy < dist_yz && dist_xy < dist_xz)
			axis = HA_XY;
		else if (dist_yz < range && dist_yz < dist_xy && dist_yz < dist_xz)
			axis = HA_YZ;
		else if (dist_xz < range && dist_xz < dist_xy && dist_xz < dist_yz)
			axis = HA_XZ;
	}
	if (_handleAxis != axis)
	{
		_handleAxis = axis;
		this->fresh();
	}
	return _handleAxis;
}
bool TransformHandler::pickScaleAxis(const BLine& line)
{
	BVector pos = _matrix.position();

	HandleAxis axis = HA_None;
	BReal disX = line.distance(_axis_x);
	BReal disY = line.distance(_axis_y);
	BReal disZ = line.distance(_axis_z);
	BReal pickRange = _axisSize / 20;
	if (disX < pickRange)
		axis = HA_X;
	if (disY < pickRange)
		axis = HA_Y;
	if (disZ < pickRange)
		axis = HA_Z;
	if (_handleAxis != axis)
	{
		_handleAxis = axis;
		this->fresh();
	}
	return _handleAxis != HA_None;
}

void TransformHandler::applyMoveOpera(const BLine& line)
{
	BVector pos = _origin.position();
	BVector begin, end;
	switch (_handleAxis)
	{
	case HA_X:
		begin = _linePressed.closest(_axis_x);
		end = line.closest(_axis_x);
		break;
	case HA_Y:
		begin = _linePressed.closest(_axis_y);
		end = line.closest(_axis_y);
		break;
	case HA_Z:
		begin = _linePressed.closest(_axis_z);
		end = line.closest(_axis_z);
		break;
	case HA_XY:
	{
		BPlane plane(_quad_xy.normal(), pos);
		_linePressed.intersect(plane, begin);
		line.intersect(plane, end);
		break;
	}
	case HA_YZ:
	{
		BPlane plane(_quad_yz.normal(), pos);
		_linePressed.intersect(plane, begin);
		line.intersect(plane, end);
		break;
	}
	case HA_XZ:
	{
		BPlane plane(_quad_xz.normal(), pos);
		_linePressed.intersect(plane, begin);
		line.intersect(plane, end);
		break;
	}
	default:
		return;
	}
	BVector offset = end - begin;
	BVector newPos = pos + offset;
	BMatrix matrix = _origin;
	matrix.setPosition(newPos);
	setMatrix(matrix);
}
void TransformHandler::applyRotateOpera(const BLine& line)
{
	BMatrix matrix = _origin;
	BVector center = matrix.position();
	BQuater rotate = matrix.rotate();
	BVector begin, end;
	switch (_handleAxis)
	{
	case HA_XY:
	{
		BVector axis = rotate * BVector(0, 0, 1);
		BPlane plane_xy(axis, center);
		_linePressed.intersect(plane_xy, begin);
		line.intersect(plane_xy, end);

		begin -= center;
		end -= center;
		begin.normalize();
		end.normalize();

		BReal angle = begin.angle(end);
		BVector normal = begin.cross(end);
		if (normal.dot(axis) < 0.0f)
			angle = -angle;

		BVector euler = matrix.euler();
		euler.z() += angle;
		matrix.setEuler(euler);
		break;
	}
	case  HA_YZ:
	{
		BVector axis = rotate * BVector(1, 0, 0);
		BPlane plane_yz(axis, center);
		_linePressed.intersect(plane_yz, begin);
		line.intersect(plane_yz, end);

		begin -= center;
		end -= center;
		begin.normalize();
		end.normalize();

		BReal angle = begin.angle(end);
		BVector normal = begin.cross(end);
		if (normal.dot(axis) < 0.0f)
			angle = -angle;

		BVector euler = matrix.euler();
		euler.x() += angle;
		matrix.setEuler(euler);
		break;
	}
	case HA_XZ:
	{
		BVector axis = rotate * BVector(0, 1, 0);
		BPlane plane_xz(axis, center);
		_linePressed.intersect(plane_xz, begin);
		line.intersect(plane_xz, end);

		begin -= center;
		end -= center;
		begin.normalize();
		end.normalize();

		BReal angle = begin.angle(end);
		BVector normal = begin.cross(end);
		if (normal.dot(axis) < 0.0f)
			angle = -angle;

		BVector euler = matrix.euler();
		euler.y() += angle;
		matrix.setEuler(euler);
		break;
	}
	default:
		break;
	}
	setMatrix(matrix);
}
void TransformHandler::applyScaleOpera(const BLine& line)
{
	BMatrix matrix = _origin;
	BVector scale = matrix.scale();
	BVector pos = matrix.position();
	BVector begin, end;
	switch (_handleAxis)
	{
	case HA_X:
	{
		begin = _linePressed.closest(_axis_x);
		end = line.closest(_axis_x);
		BReal length = (begin - _axis_x.begin()).length();
		BReal ratio = (end - _axis_x.begin()).length() / length;
		scale.x() *= ratio;
		break;
	}
	case HA_Y:
	{
		begin = _linePressed.closest(_axis_y);
		end = line.closest(_axis_y);
		BReal length = (begin - _axis_y.begin()).length();
		BReal ratio = (end - _axis_y.begin()).length() / length;
		scale.y() *= ratio;
		break;
	}
	case HA_Z:
	{
		begin = _linePressed.closest(_axis_z);
		end = line.closest(_axis_z);
		BReal length = (begin - _axis_z.begin()).length();
		BReal ratio = (end - _axis_z.begin()).length() / length;
		scale.z() *= ratio;
		break;
	}
	default:
		return;
	}
	matrix.setScale(scale);
	setMatrix(matrix);
}

void TransformHandler::drawAnchorAxis(BRender& render)
{
	BVector center = _matrix.position();

	BReal half = _axisSize / 2;
	BLine axis_x(center - BVector(half, 0, 0), center + BVector(half, 0, 0));
	BLine axis_y(center - BVector(0, half, 0), center + BVector(0, half, 0));
	BLine axis_z(center - BVector(0, 0, half), center + BVector(0, 0, half));

	render.setCullback(false);
	render.setDepthTest(false);

	render.setColor(255, 255, 255, 128);
	render.drawVertex(center);

	render.setColor(255, 0, 0, 200);
	render.drawLine(axis_x);

	render.setColor(0, 255, 0, 200);
	render.drawLine(axis_y);

	render.setColor(0, 0, 255, 200);
	render.drawLine(axis_z);

}
void TransformHandler::drawMoveAxis(BRender& render)
{
	render.setCullback(false);
	render.setDepthTest(false);

	BReal side = _axisSize * (BReal)0.1;
	if (_handleAxis & HA_X)
		render.setColor(255, 255, 0, 255);
	else
		render.setColor(255, 0, 0, 255);
	render.drawLine(_axis_x);
	_cone_x->setColor(render.color());
	render.drawShape(_cone_x);

	if (_handleAxis & HA_Y)
		render.setColor(255, 255, 0, 255);
	else
		render.setColor(0, 255, 0, 255);
	render.drawLine(_axis_y);
	_cone_y->setColor(render.color());
	render.drawShape(_cone_y);

	if (_handleAxis & HA_Z)
		render.setColor(255, 255, 0, 255);
	else
		render.setColor(0, 0, 255, 255);
	render.drawLine(_axis_z);
	_cone_z->setColor(render.color());
	render.drawShape(_cone_z);

	if (_handleAxis == HA_XY)
		render.setColor(255, 255, 0, 200);
	else
		render.setColor(255, 255, 0, 76);
	render.fillQuad(_quad_xy);

	if (_handleAxis == HA_YZ)
		render.setColor(255, 255, 0, 200);
	else
		render.setColor(0, 255, 255, 76);
	render.fillQuad(_quad_yz);

	if (_handleAxis == HA_XZ)
		render.setColor(255, 255, 0, 200);
	else
		render.setColor(255, 0, 255, 76);
	render.fillQuad(_quad_xz);
}
void TransformHandler::drawRotateAxis(BRender& render)
{
	render.pushAttrib();
	render.pushMatrix(_matrix);
	render.setLighting(false);

	if (_handleAxis)
		_rotateSphere->setColor(255, 255, 0, 200);
	else
		_rotateSphere->setColor(128, 128, 128, 200);
	render.drawShape(_rotateSphere);

	if (_handleAxis == HA_XY)
		render.setColor(255, 255, 0, 255);
	else
		render.setColor(255, 0, 0, 255);
	render.drawLineLoop(_rotateCircleXY);

	if (_handleAxis == HA_YZ)
		render.setColor(255, 255, 0, 255);
	else
		render.setColor(0, 255, 0, 255);
	render.drawLineLoop(_rotateCircleYZ);

	if (_handleAxis == HA_XZ)
		render.setColor(255, 255, 0, 255);
	else
		render.setColor(0, 0, 255, 255);
	render.drawLineLoop(_rotateCircleXZ);

	render.popMatrix();
	render.popAttrib();

}
void TransformHandler::drawScaleAxis(BRender& render)
{
	render.setCullback(false);
	render.setDepthTest(false);

	BReal side = _axisSize * (BReal)0.1;
	if (_handleAxis & HA_X)
		render.setColor(255, 255, 0, 255);
	else
		render.setColor(255, 0, 0, 255);
	render.drawLine(_axis_x);
	render.drawQuad(_box_x.v0(), _box_x.v4(), _box_x.v7(), _box_x.v3());
	render.drawQuad(_box_x.v1(), _box_x.v2(), _box_x.v6(), _box_x.v5());

	if (_handleAxis & HA_Y)
		render.setColor(255, 255, 0, 255);
	else
		render.setColor(0, 255, 0, 255);
	render.drawLine(_axis_y);
	render.drawQuad(_box_y.v0(), _box_y.v1(), _box_y.v5(), _box_y.v4());
	render.drawQuad(_box_y.v2(), _box_y.v3(), _box_y.v7(), _box_y.v6());

	if (_handleAxis & HA_Z)
		render.setColor(255, 255, 0, 255);
	else
		render.setColor(0, 0, 255, 255);
	render.drawLine(_axis_z);
	render.drawQuad(_box_z.v4(), _box_z.v5(), _box_z.v6(), _box_z.v7());
	render.drawQuad(_box_z.v0(), _box_z.v1(), _box_z.v2(), _box_z.v3());
}

void TransformHandler::slotTargetChanged(BObject* object, const BValue& value)
{
	Target* target = value;
}
void TransformHandler::slotCameraFocusFinished(BObject* object, const BValue& value)
{
	_dirtyAxis = true;
	this->fresh();
}

void TransformHandler::render(BRender& render)
{
	render.setLighting(false);
	render.setLineSmooth(true);
	render.setColor(255, 255, 255);

	render.pushMatrix(_basicMatrix);
	switch (_handleMode)
	{
	case HM_None:
		drawAnchorAxis(render);
		break;
	case HM_Move:
		drawMoveAxis(render);
		break;
	case HM_Rotate:
		drawRotateAxis(render);
		break;
	case HM_Scale:
		drawScaleAxis(render);
		break;
	default:
		break;
	}
	render.popMatrix();
}

void TransformHandler::attachEvent(const BEvent& event)
{
	
}
void TransformHandler::detachEvent(const BEvent& event)
{
	
}

void TransformHandler::freshEvent(const BEvent& event)
{
	if (_dirtyAxis)
	{
		BMatrix matrix = _basicMatrix * _matrix;
		BVector center = matrix.position();
		BReal unit = this->viewer()->unit(center);
		BReal axisSize = unit * 120;
		if (_axisSize != axisSize)
		{
			_axisSize = axisSize;
			freshMoveAxis();
			freshRotateAxis();
			freshScaleAxis();
		}
		_dirtyAxis = false;
	}
}

void TransformHandler::keyPressEvent(const BKeyEvent& keyEvent)
{

}
void TransformHandler::keyReleaseEvent(const BKeyEvent& keyEvent)
{

}

void TransformHandler::mouseEnterEvent(const BMouseEvent& mouseEvent)
{

}
void TransformHandler::mouseLeaveEvent(const BMouseEvent& mouseEvent)
{

}
void TransformHandler::mousePressEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		const BPoint& pos = mouseEvent.pos();
		BLine line = this->viewer()->line(pos);
		if (_handleAxis)
		{
			_leftPressed = true;
			_linePressed = line;
			startTransform(_matrix);
			mouseEvent.accept();
			return;
		}
	}
	BHandler::mousePressEvent(mouseEvent);
}
void TransformHandler::mouseReleaseEvent(const BMouseEvent& mouseEvent)
{
	if (_leftPressed)
	{
		if (_matrix != _origin)
		{
			finishTransform(_matrix);
			_origin = _matrix;
			_dirtyAxis = true;
			this->fresh();
		}
		_leftPressed = false;
		mouseEvent.accept();
		return;
	}
	BHandler::mouseReleaseEvent(mouseEvent);
}
void TransformHandler::mouseMoveEvent(const BMouseEvent& mouseEvent)
{
	const BPoint& pos = mouseEvent.pos();
	BLine line = this->viewer()->line(pos);
	if (mouseEvent.button() == Button_None)
	{
		const BPoint& pos = mouseEvent.pos();
		BLine line = this->viewer()->line(pos);
		switch (_handleMode)
		{
		case HM_Move:
			pickMoveAxis(line);
			mouseEvent.accept();
			break;
		case HM_Rotate:
			pickRotateAxis(line);
			mouseEvent.accept();
			break;
		case HM_Scale:
			pickScaleAxis(line);
			mouseEvent.accept();
			break;
		default:
			break;
		}
		return;
	}
	else if (mouseEvent.button() == Button_Left)
	{
		if (_handleMode)
		{
			if (_handleMode == HM_Move)
				applyMoveOpera(line);
			mouseEvent.accept();
			if (_handleMode == HM_Rotate)
				applyRotateOpera(line);
			if (_handleMode == HM_Scale)
				applyScaleOpera(line);
			_dirtyAxis = true;
			transforming(_matrix);
			this->fresh();
			mouseEvent.accept();
			return;
		}
	}
	BHandler::mouseMoveEvent(mouseEvent);
}

void TransformHandler::mouseWheelEvent(const BMouseEvent& mouseEvent)
{
	int wheel = mouseEvent.wheel();
	BMatrix matrix = _basicMatrix * _matrix;
	BVector center = matrix.position();
	BReal unit = this->viewer()->unit(center);
	BReal axisSize = unit * 120;
	BReal mults = axisSize / _axisSize;
	if (mults < 0.3333 || mults > 3.0)
	{
		_dirtyAxis = true;
		this->fresh();
	}
}
